﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using System.Web.Caching;
using System.Web.Hosting;
using System.Xml.Linq;

namespace Extend.Framework
{
    public class AreaVirtualPathProvider : VirtualPathProvider
    {
        /// <summary>
        /// 记录主项目的物理路径
        /// </summary>
        private readonly string basePath = Path.GetFullPath(HostingEnvironment.MapPath("~") + @"Plugin");

        private static List<KeyValuePair<string, string>> areaVirtualPathList = new List<KeyValuePair<string, string>>();
        /// <summary>
        /// 获取项目中所有注册的模块路径关系
        /// </summary>
        static AreaVirtualPathProvider()
        {
            //AppDomain.CurrentDomain.SetupInformation.PrivateBinPath = "";

            var assemblies = AppDomain.CurrentDomain.GetAssemblies();
            foreach (var assembly in assemblies)
            {
                foreach (var type in assembly.GetExportedTypes())
                {
                    if (Array.Exists(type.GetInterfaces(), t => t.Name.Equals("IAreaVirtualPathRegistration")))
                    {
                        var areaVirtualPathRegistration = assembly.CreateInstance(type.FullName) as IAreaVirtualPathRegistration;
                        foreach (var areaVirtualPath in areaVirtualPathRegistration.GetPath())
                        {
                            var key = @"/Areas/" + areaVirtualPath.Key;
                            var value = areaVirtualPath.Value;

                            areaVirtualPathList.Add(new KeyValuePair<string, string>(key, value));
                        }
                    }
                }
            }
        }

        /// <summary>
        /// 请求的虚拟路径转换为本地物理路径
        /// </summary>
        /// <param name="virtualPath"></param>
        /// <returns></returns>
        private string GetRealPath(string virtualPath)
        {
            if (virtualPath.StartsWith("~"))
            {
                virtualPath = VirtualPathUtility.ToAbsolute(virtualPath);
            }

            foreach (var areaVirtualPath in areaVirtualPathList)
            {
                if (virtualPath.StartsWith(areaVirtualPath.Key, StringComparison.OrdinalIgnoreCase))
                {
                    var realPath = Path.Combine(basePath, virtualPath.Replace(areaVirtualPath.Key, areaVirtualPath.Value));

                    if (File.Exists(realPath))
                    {
                        return realPath;
                    }
                }
            }

            return null;
        }



        public override bool FileExists(string virtualPath)
        {
            var realPath = GetRealPath(virtualPath);
            if (realPath != null)
            {
                return true;
            }
            return base.FileExists(virtualPath);
        }



        public override CacheDependency GetCacheDependency(string virtualPath, IEnumerable virtualPathDependencies, DateTime utcStart)
        {
            var realPath = GetRealPath(virtualPath);
            if (realPath != null)
            {
                var filePathList = new List<string>();
                foreach (var virtualPath3 in virtualPathDependencies)
                {
                    filePathList.Add(GetRealPath(virtualPath3.ToString()));
                }

                return new CacheDependency(filePathList.ToArray(), utcStart);
            }

            return base.GetCacheDependency(virtualPath, virtualPathDependencies, utcStart);
        }



        public override string GetFileHash(string virtualPath, IEnumerable virtualPathDependencies)
        {
            var realPath = GetRealPath(virtualPath);
            if (realPath != null)
            {
                var filePathList = new List<string>();
                foreach (var virtualPath3 in virtualPathDependencies)
                {
                    filePathList.Add(GetRealPath(virtualPath3.ToString()));
                }

                return string.Join(string.Empty, filePathList.ToArray()).GetHashCode().ToString();
            }

            return base.GetFileHash(virtualPath, virtualPathDependencies);
        }


        public override VirtualFile GetFile(string virtualPath)
        {
            var realPath = GetRealPath(virtualPath);
            if (realPath != null)
            {
                var viewStream = new FileStream(realPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
                var webConfigFileStream = new FileStream(GetWebConfigFullPath(virtualPath), FileMode.Open, FileAccess.Read, FileShare.ReadWrite);

                return new AreaVirtualFile(virtualPath, CorrectView(virtualPath, viewStream, webConfigFileStream));
            }

            return base.GetFile(virtualPath);
        }


        private string GetWebConfigFullPath(string viewVirtualPath)
        {
            var realPath = Path.GetDirectoryName(GetRealPath(viewVirtualPath));
            while (realPath.Contains("\\"))
            {
                var webConfigPath = realPath + @"\Web.config";
                if (File.Exists(webConfigPath))
                {
                    return webConfigPath;
                }

                realPath = realPath.Substring(0, realPath.LastIndexOf('\\'));
            }

            return Path.GetFullPath(HostingEnvironment.MapPath("~/Views/Web.Config"));
        }


        private Stream CorrectView(string virtualPath, Stream stream, Stream webconfigStream)
        {
            var view = string.Empty;
            using (stream)
            using (var reader = new StreamReader(stream, Encoding.UTF8))
            {
                view = reader.ReadToEnd();
            }

            var namespaces = new List<string>();
            var pageBaseType = string.Empty;
            var layoutPath = string.Empty;
            if (webconfigStream != null)
            {
                using (webconfigStream)
                {
                    XDocument xdocument = XDocument.Load(webconfigStream);
                    namespaces =
                        xdocument.Root.Descendants("namespaces")
                            .Descendants()
                            .Select(x => x.Attribute("namespace").Value)
                            .ToList();
                    pageBaseType =
                        xdocument.Root.Descendants("system.web.webPages.razor")
                            .Descendants("pages")
                            .Select(x => x.Attribute("pageBaseType").Value)
                            .FirstOrDefault();
                }
            }

            var ourStream = new MemoryStream();
            var writer = new StreamWriter(ourStream, Encoding.UTF8);
            var modelString = string.Empty;
            var modelPos = view.IndexOf("@model");
            if (modelPos != -1)
            {
                writer.Write(view.Substring(0, modelPos));
                int modelEndPos = view.IndexOfAny(new[] { '\r', '\n' }, modelPos);
                modelString = view.Substring(modelPos, modelEndPos - modelPos);
                view = view.Remove(0, modelEndPos);
            }

            if (namespaces.Count > 0)
            {
                foreach (string namespaceConfig in namespaces)
                {
                    writer.WriteLine("@using " + namespaceConfig);
                }
            }
            else
            {
                writer.WriteLine("@using System.Web.Mvc");
                writer.WriteLine("@using System.Web.Mvc.Ajax");
                writer.WriteLine("@using System.Web.Mvc.Html");
                writer.WriteLine("@using System.Web.Routing");
            }

            string basePrefix = "@inherits " + pageBaseType;

            if (virtualPath.ToLower().Contains("_viewstart"))
            {
                writer.WriteLine("@inherits System.Web.WebPages.StartPage");
            }
            else if (modelString == "@model object")
            {
                writer.WriteLine(basePrefix + "<dynamic>");
            }
            else if (!string.IsNullOrEmpty(modelString))
            {
                writer.WriteLine(basePrefix + "<" + modelString.Substring(7) + ">");
            }
            else
            {
                writer.WriteLine(basePrefix + "<dynamic>");
            }

            // 分部视图不能有layout
            if (!string.IsNullOrEmpty(layoutPath) && !virtualPath.Contains("/_"))
            {
                writer.WriteLine("@{{ Layout = \"{0}\"; }}", layoutPath);
            }

            writer.Write(view);
            writer.Flush();

            ourStream.Position = 0;

            return ourStream;
        }
    }
}
